package com.wolfking.spi;

import org.springframework.util.StringUtils;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;

/**
 * 经过隔壁老王处理的JDK动态代理的实现
 * spi service的动态代理
 * Object的方法调用this的方法
 * 其他方法根据SpiApplicationContext的Threadlocal动态选择Service
 *
 * @author 赵伟伟(wolfking)
 * created on 2019-09-29 13:33
 */
class JdkSpiProxy implements InvocationHandler {

    /**
     * spi的代理接口
     */
    private final Class<?> spiInterface;
    /**
     * spring里面不同spi的实现的类
     */
    private final Map<String, Object> beans;

    private final SpiInterface spiInterfaceAnna;

    JdkSpiProxy(Class<?> spiInterface, Map<String, Object> beans) {
        this.beans = beans;
        this.spiInterface = spiInterface;
        this.spiInterfaceAnna = spiInterface.getAnnotation(SpiInterface.class);
    }

    /**
     * 动态代理方法调用
     *
     * @param proxy  代理类
     * @param method 当前方法
     * @param args   参数
     * @return 处理结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.isDefault()) {
            /*default的方法*/
            Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
            constructor.setAccessible(true);
            Class<?> declaringClass = method.getDeclaringClass();
            int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED | MethodHandles.Lookup.PACKAGE;
            return constructor.newInstance(declaringClass, allModes)
                    .unreflectSpecial(method, declaringClass)
                    .bindTo(proxy)
                    .invokeWithArguments(args);
        } else if (method.getDeclaringClass().equals(Object.class)) {
            return method.invoke(this, args);
        } else {
            String code = SpiApplicationContext.getSpiCode();
            if (!StringUtils.hasText(code)) {
                code = spiInterfaceAnna.defaultCode();
            }
            return method.invoke(beans.get(code), args);
        }
    }

    /**
     * 获取代理对象呢
     *
     * @return Spi的动态代理
     */
    Object getSpiProxy() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{spiInterface}, this);
    }
}
